Computer programmers often write computer programs in human-understandable source code that conforms to the syntax of some programming language. Modern programming languages in which source code might be written include JAVA and C++. Because computer programmers are fallible and because programming can sometimes be a complicated task, computer programmers sometimes introduce mistakes into their source code when writing a computer program. Sometimes these mistakes will cause the program to throw an error when executed. Under such circumstances, a mistake in the program might be easily detected and repaired. However, other mistakes might not cause the execution of the program to halt. For example, some kinds of mistakes will permit the computer program to continue to execute, but will cause the computer program to behave in a way that the computer programmer did not intend. These non-fatal errors can be more difficult to detect and repair.
In order to assist computer programmers in tracking down the errors that are causing a computer program to exhibit unintended behavior, debugging tools are often provided in a computer programming environment. Some debugging tools allow computer programmers to insert breakpoints at specified lines within source code. Such debugging tools simulate the execution of the program up until a line at which a breakpoint has been inserted, and then pause the execution of the program so that the programmer can examine the state of the computer (e.g., memory, output, etc.) at that breakpoint. Programmers can use such breakpoints to determine at which line of a computer program unintended behavior is beginning to occur.
Additionally, some debugging tools allow computer programmers to “step” through the execution of a computer program, line-by-line in the source code. After simulating the execution of a single line of source code, the debugging tool pauses the execution of the program as with the breakpoints discussed above. Upon the programmer's instruction to continue execution (e.g., by pressing a specified key), the debugging tool proceeds to execute the following line in the source code, and then pauses execution again. In this manner, the computer programmer can use the debugging tool to execute the computer program a source code line at a time. After the execution of each source code line, the computer programmer can take the time to examine the effects of the execution of that source code line on the computer's state. This helps the computer programmer to identify the particular source code lines that are causing a computer program to exhibit unintended behavior. When the computer programmer identifies a line that is causing the unintended effects, the computer programmer can then modify that line so that the computer program no longer exhibits the unintended behavior.
Although debugging tools such as those described above can be invaluable in assisting a computer programmer, such debugging tools have historically suffered from some significant limitations. As is discussed above, breakpoints and stepping have traditionally been implemented on a line-by-line basis. Typically, the smallest unit that a debugging tool can execute before pausing a computer program's execution is an entire line of source code. This limitation can be troublesome under circumstances in which a single line of source code contains multiple different expressions. When a source code line contains multiple different expressions, a computer programmer might have some difficulty identifying the particular one of those expressions that is contributing to the program's erratic behavior.
For example, a single line in the source code of a computer program might contain an expression such as “m(f( ) f( ).” The evaluation of such an expression causes the method “m( )” to be invoked with the results of two separate invocations of the method “f( )” as arguments. Thus, before the invocation of method “m( )” can be completely evaluated, each of the invocations of method “f( )” needs to be evaluated. Each one of these three method invocations is a separate expression in its own right; “f( )” and “f( )” are “subexpressions” of the “m(f( ) f( )” expression. Each such expression has a separate set of inputs (e.g., arguments) and a separate set of outputs (e.g., return values).
Any one (or more) of these three method invocations could be leading to erratic results produced during the computer program's execution. Indeed, the programmer might deduce, using debugging tools, that at least one of these method invocations is the source of the erratic results. However, because all three of these method invocations are present within the same line of source code, the programmer might have a very difficult time determining which one (or more) of these method invocations is causing the erratic behavior. Using traditional debugging tools, the programmer has no way of pausing the program's execution after the first invocation of method “f( )” but before the second invocation of method “f( ),” or after the second invocation of method “f( )” but before the invocation of method“m( ).”